﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Text.RegularExpressions;
using System.Threading;

namespace ROOTFileSizeAnalyzer.Parsers
{
    /// <summary>
    /// GIven a sequence of lines from some source (file, uri, etc,) return a bunch
    /// of branch info guys.
    /// </summary>
    public class LineToBranchConverter : IObservable<ROOTBranchInfo>, IObserver<string>
    {
        IObserver<ROOTBranchInfo> _watcher = null;
        ManualResetEvent _watcherGood = new ManualResetEvent(false);

        private class KillMe : IDisposable
        {
            LineToBranchConverter _owner = null;
            public KillMe(LineToBranchConverter owner)
            {
                _owner = owner;
            }
            public void Dispose()
            {
                _owner._watcherGood.Reset();
                _owner._watcher = null;
            }
        }

        /// <summary>
        /// Setup a link to this copy.
        /// </summary>
        /// <param name="observer"></param>
        /// <returns></returns>
        public IDisposable Subscribe(IObserver<ROOTBranchInfo> observer)
        {
            if (_watcher != null)
            {
                throw new InvalidOperationException("Can't add a second observer as one is already going");
            }
            _watcher = observer;
            _watcherGood.Set();

            return new KillMe(this);
        }

        /// <summary>
        /// If there is anything left to parse, then do the parsing.
        /// </summary>
        public void OnCompleted()
        {
            _watcherGood.WaitOne();
            if (_info != null)
            {
                _watcher.OnNext(_info);
            }
            _watcher.OnCompleted();
        }

        /// <summary>
        /// We pass the error on through.. Nothing else is going to happen!
        /// </summary>
        /// <param name="exception"></param>
        public void OnError(Exception exception)
        {
            _watcherGood.WaitOne();
            _watcher.OnError(exception);
            _watcherGood.Reset();
        }

        /// *Br    0 :run       : run/i                                                  *
        Regex findBranchLine = new Regex("\\*Br\\s+(?<Number>[0-9]+)\\s*:(?<Name>[^\\s]+)\\s*:\\s*(?<Type>[^\\s\\*]+)");
        /// *Br    0 :run
        /// And then there is a continue line
        Regex findBranchLineTruncated = new Regex("\\*Br\\s+(?<Number>[0-9]+)\\s*:(?<Name>[^\\s]+)\\s*:");
        bool BranchLineWasTruncated = false;
        /// *         | muonassoc_ptrelAK4H1TPJ[njetsAK4H1TPJ][20]/F                     *
        Regex findBranchContinueLine = new Regex("\\|\\s*(?<Type>[^\\s]+)");
        /// *Entries :     4992 : Total  Size=      20506 bytes  File Size  =      20040 *
        Regex findEntriesLine = new Regex("\\*Entries :\\s*(?<Entries>[0-9]+)[^0-9]+(?<TotalSize>[0-9]+)[^0-9]+(?<FileSize>[0-9]+)");
        /// *Baskets :        1 : Basket Size=      32000 bytes  Compression=   1.00     *
        Regex findBasketsLine = new Regex("\\*Baskets :[^0-9]+(?<NBaskets>[0-9]+)[^0-9]+(?<BSize>[0-9]+)[^0-9]+(?<Compression>[0-9\\.]+)");


        ROOTBranchInfo _info = null;

        public void OnNext(string line)
        {
            ///
            /// Make sure we have someone to send to!
            /// 

            _watcherGood.WaitOne();
            if (_watcher == null)
                return;

            /// Basic skimming
            if (line == null)
                return;
            line = line.Trim();
            if (line.Length == 0)
                return;

            /// Now, see if this is the start of a new branch.

            if (line.IndexOf("*Br ") == 0)
            {
                if (_info != null)
                {
                    _watcher.OnNext(_info);
                    _info = null;
                }
                _info = new ROOTBranchInfo();
            }

            /// Now, see what it matches, and then extract the groups from it and
            /// fill in what we know!

            if (_info != null)
            {
                Match blinfo;

                /// Anything left over from last time through ( a truncated line )
                if (BranchLineWasTruncated)
                {
                    BranchLineWasTruncated = false;
                    blinfo = findBranchContinueLine.Match(line);
                    if (blinfo.Success)
                    {
                        _info.BranchType = blinfo.Groups["Type"].Value;
                    }
                }

                /// Now, go after the normal stuff.
                blinfo = findBranchLine.Match(line);
                if (blinfo.Success)
                {
                    _info.BranchNumber = Convert.ToInt32(blinfo.Groups["Number"].Value);
                    _info.BranchName = blinfo.Groups["Name"].Value;
                    _info.BranchType = blinfo.Groups["Type"].Value;
                }
                else
                {
                    blinfo = findBranchLineTruncated.Match(line);
                    if (blinfo.Success)
                    {
                        _info.BranchNumber = Convert.ToInt32(blinfo.Groups["Number"].Value);
                        _info.BranchName = blinfo.Groups["Name"].Value;
                        BranchLineWasTruncated = true;
                    }
                }

                blinfo = findEntriesLine.Match(line);
                if (blinfo.Success)
                {
                    _info.Entries = Convert.ToInt32(blinfo.Groups["Entries"].Value);
                    _info.TotalSize = Convert.ToInt32(blinfo.Groups["TotalSize"].Value);
                    _info.TotalSizeOnDisk = Convert.ToInt32(blinfo.Groups["FileSize"].Value);
                }

                blinfo = findBasketsLine.Match(line);
                if (blinfo.Success)
                {
                    _info.NumberBaskets = Convert.ToInt32(blinfo.Groups["NBaskets"].Value);
                    _info.BasketSize = Convert.ToInt32(blinfo.Groups["BSize"].Value);
                    _info.CompressionFactor = Convert.ToDouble(blinfo.Groups["Compression"].Value);
                }
            }

        }
    }
}
